package csci4448.android.workouttracker;

import android.content.Context;
import android.location.Location;
import android.os.SystemClock;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class RecordTabController
{
	public static final double MILLISECONDS_PER_SECOND = 1000;
	
	//Keeps track of the current state of the recorder
	private RecordingState _state;
	private Session _inProgressSession;
	
	//Keeps track of when this particular GPSRecorder object started
	//For use in storing the GPS data to a persistent store
	private Date _startTime;
	private Date _endTime;
	
	//To accommodate pausing we need to keep track of the total elapsed time
	//and a reference start time separately; such that we can increment the
	//elapsed time on every pause event, and reset the reference start time
	//on every resume event
	private long _referenceStartElapsedTime;
	private long _elapsedTime;
	private List<RecordTabControllerEventListener> _updateEventListeners;
	
	//Location tracking members
	private GPSModel _locationModel;
	private GPSModelEventUpdateHandler _modelUpdateHandler;
	private boolean _locationSet;
	private double _lastLatitude;
	private double _lastLongitude;
	private double _distanceTravelledMeters;
	
	//Persistence model
	private SessionModel _persistenceModel;
	
	private ScheduledExecutorService _updateExecutor;
	public RecordTabController(Context context){
		//Setup initial recorder state
		_state = RecordingState.Idle;
		_startTime = new Date();
		_endTime = new Date();
		_elapsedTime = 0;
		_referenceStartElapsedTime = 0;
		_distanceTravelledMeters = 0.0f;
		_locationSet = false;
		_updateEventListeners = new ArrayList<RecordTabControllerEventListener>();
		_locationModel = new GPSModel(context);
		_modelUpdateHandler = new GPSModelEventUpdateHandler(this);
		_persistenceModel = new SessionModel(context);
		SessionFactory sf = new SessionFactory();
		try {
			_inProgressSession = sf.createSession(-1, getTotalMillisecondsElapsed(), _distanceTravelledMeters, _startTime.toString(), null, null);
		} catch (Exception e) {}
	}
	
	//Get the current state of the recorder
	public RecordingState getRecordingState()
	{
		return _state;
	}
	
	//Subscribe to GPSRecorder updates
	public synchronized void addEventListener(RecordTabControllerEventListener listener)
	{
		_updateEventListeners.add(listener);
	}
	
	//Unsubscribe from GPSRecorder updates
	public synchronized void removeEventListener(RecordTabControllerEventListener listener)
	{
		_updateEventListeners.remove(listener);
	}
	
	//Provide a class that can be instantiated and provided to the execution service
	class GPSModelEventUpdateHandler implements Runnable {
		private RecordTabController _controller;
		private GPSModelEventUpdateHandler(RecordTabController Controller)
		{
			_controller = Controller;
		}
		public void run() {
			if(_state == RecordingState.Recording && _locationSet && _locationModel.hasLocation()){
				float[] results = new float[3];
				Location.distanceBetween(_lastLatitude, _lastLongitude, _locationModel.getLatitude(), _locationModel.getLongitude(), results);
				_distanceTravelledMeters += results[0];
				_lastLatitude = _locationModel.getLatitude();
				_lastLongitude = _locationModel.getLongitude();
			} else if(_locationModel.hasLocation()){
				_lastLatitude = _locationModel.getLatitude();
				_lastLongitude = _locationModel.getLongitude();
				_locationSet = true;
			}
			_inProgressSession.setDistanceMeters(_distanceTravelledMeters);
			_inProgressSession.setDurationSeconds(getTotalSecondsElapsed());
			if(_controller._state == RecordingState.Recording || _controller._state == RecordingState.Finished)
			{
				RecordTabControllerEvent event = new RecordTabControllerEvent(_controller);
			    Iterator<RecordTabControllerEventListener> i = _updateEventListeners.iterator();
			    while(i.hasNext())  {
			      i.next().handleGPSRecorderEvent(event);
			    }
			}
		}
		
	}
	
	//Commit the elapsed time to a time accumulator
	//This is needed due to the pause/resume functionality we have
	private void commitElapsedTimeDelta()
	{
		_elapsedTime += ((_referenceStartElapsedTime > 0) ? (SystemClock.elapsedRealtime() - _referenceStartElapsedTime) : 0);
		_referenceStartElapsedTime = 0;
	}
	
	private long getTotalSecondsElapsed()
	{
		return Math.round(getTotalMillisecondsElapsed() / MILLISECONDS_PER_SECOND);
	}
	//Get the duration of travel
	private long getTotalMillisecondsElapsed()
	{
		return _elapsedTime + ((_referenceStartElapsedTime > 0) ? (SystemClock.elapsedRealtime() - _referenceStartElapsedTime) : 0);
	}
	
	public Session getInProgressSession()
	{
		return _inProgressSession;
	}
	//Handle Start and Finish events
	public RecordingState ToggleStartFinishState()
	{
		switch(_state)
		{
			case Recording:
				commitElapsedTimeDelta();
			case Paused:
				//Finish recording
				_state = RecordingState.Finished;
				_modelUpdateHandler.run();
				_endTime = new Date();
				_updateExecutor.shutdown();
				_inProgressSession.setEndTime(_endTime.toString());
				_inProgressSession.setDistanceMeters(_distanceTravelledMeters);
				_inProgressSession.setDurationSeconds(getTotalSecondsElapsed());
				//Save current session data to persistent store
				break;
			case Idle:
			case Cancelled:
			case Finished:
				//Start recording
				_distanceTravelledMeters = 0;
				_startTime = new Date();
				_elapsedTime = 0;
				_state = RecordingState.Recording;
				_referenceStartElapsedTime = SystemClock.elapsedRealtime();
				_updateExecutor = Executors.newSingleThreadScheduledExecutor();
				_updateExecutor.scheduleAtFixedRate(_modelUpdateHandler, 0, 1000, TimeUnit.MILLISECONDS);
				break;
		}
		return _state;
	}
	
	//Handle Pause and Resume events
	public RecordingState TogglePauseResumeState()
	{
		switch(_state)
		{
			case Paused:
				//Resume
				_state = RecordingState.Recording;
				_referenceStartElapsedTime = SystemClock.elapsedRealtime();
				break;
			case Recording:
				//Pause recording
				_state = RecordingState.Paused;
				commitElapsedTimeDelta();
				break;
			case Idle:
			case Cancelled:
			case Finished:
				//Throw
				break;
		}
		return _state;
	}
	
	//Handle Cancel event
	public RecordingState CancelRecording()
	{
		switch(_state)
		{
			case Paused:
			case Recording:
				//Finish recording
				_state = RecordingState.Cancelled;
				_updateExecutor.shutdown();
				_elapsedTime = 0;
				_referenceStartElapsedTime = 0;
				break;
			case Idle:
			case Cancelled:
			case Finished:
				//Throw
				break;
		}
		return _state;
	}

	public void saveSession(String sessionName)
	{
		_inProgressSession.setSessionName(sessionName);
		_persistenceModel.addSession(_inProgressSession);
	}
}
